1 Descripción del dataset

El conjunto de datos del titanic es ampliamente concido en la comunidad del ML. Es más, forma parte de los retos de iniciación en la plataforma kaggle.

Este conjunto de datos, es la representación de las personas que embarcaron en el titanic. En el, se recogen multitud de datos sobre cada persona, relativos a su edad, pais y clase en la que embarcaron, además de si sobrevivieron o no.

1.1 ¿Por qué es importante?

Este cojunto de datos tiene la posibilidad de explicar algunos datos de la catastrofe. Puede aclarar si hubo algún condicionante para la muerte o supervivencia de las personas más allá del puro azar.

1.2 ¿Qué pregunta pretende responder?

Este cojunto de datos pretende elaborar un modelo predictivo y con el responder a la pregunta: ¿Qué tipo de personas tenían más probabilidades de sobrevivir?

2 Integración de los datos de interés a analizar

Antes de integrar, vamos a describir las variables que caracterízan a estos datos:

Escogemos las columnas que serán factores:

factors = c("Sex"="factor","Pclass"="factor","Cabin"="factor", "Embarked"="factor","SibSp"="factor","Parch"="factor","Survived"="factor");

Cargamos los datos:

titanicData <- read.csv('titanic.csv', stringsAsFactors = FALSE, colClasses = factors );
summary(titanicData);
##   PassengerId    Survived Pclass      Name               Sex     
##  Min.   :  1.0   0:549    1:216   Length:891         female:314  
##  1st Qu.:223.5   1:342    2:184   Class :character   male  :577  
##  Median :446.0            3:491   Mode  :character               
##  Mean   :446.0                                                   
##  3rd Qu.:668.5                                                   
##  Max.   :891.0                                                   
##                                                                  
##       Age        SibSp   Parch      Ticket               Fare       
##  Min.   : 0.42   0:608   0:678   Length:891         Min.   :  0.00  
##  1st Qu.:20.12   1:209   1:118   Class :character   1st Qu.:  7.91  
##  Median :28.00   2: 28   2: 80   Mode  :character   Median : 14.45  
##  Mean   :29.70   3: 16   3:  5                      Mean   : 32.20  
##  3rd Qu.:38.00   4: 18   4:  4                      3rd Qu.: 31.00  
##  Max.   :80.00   5:  5   5:  5                      Max.   :512.33  
##  NA's   :177     8:  7   6:  1                                      
##          Cabin     Embarked
##             :687    :  2   
##  B96 B98    :  4   C:168   
##  C23 C25 C27:  4   Q: 77   
##  G6         :  4   S:644   
##  C22 C26    :  3           
##  D          :  3           
##  (Other)    :186

Los datos de interés de ese cojunto de datos serán los que nos aporten algo de información sobre las personas pero a nivel de cojunto, es decir datos como nombre, identificador de pasajero o número de ticket no nos resultan de utilidad. Por lo que podemos crear un conjunto de datos solamente con los datos adecuados:

cols_remove <- c("PassengerId", "Name", "Ticket")
titanicData <- titanicData[, !(colnames(titanicData) %in% cols_remove)]
summary(titanicData);
##  Survived Pclass      Sex           Age        SibSp   Parch        Fare       
##  0:549    1:216   female:314   Min.   : 0.42   0:608   0:678   Min.   :  0.00  
##  1:342    2:184   male  :577   1st Qu.:20.12   1:209   1:118   1st Qu.:  7.91  
##           3:491                Median :28.00   2: 28   2: 80   Median : 14.45  
##                                Mean   :29.70   3: 16   3:  5   Mean   : 32.20  
##                                3rd Qu.:38.00   4: 18   4:  4   3rd Qu.: 31.00  
##                                Max.   :80.00   5:  5   5:  5   Max.   :512.33  
##                                NA's   :177     8:  7   6:  1                   
##          Cabin     Embarked
##             :687    :  2   
##  B96 B98    :  4   C:168   
##  C23 C25 C27:  4   Q: 77   
##  G6         :  4   S:644   
##  C22 C26    :  3           
##  D          :  3           
##  (Other)    :186

3 Limpieza de los datos.

3.1 ¿Los datos contienen ceros o elementos vacíos? ¿Cómo gestionarías cada uno de estos casos?

Para comprobar si los datos contienen elementos vacíos se ejecuta la siguiente sentencia.

colSums(is.na(titanicData))
## Survived   Pclass      Sex      Age    SibSp    Parch     Fare    Cabin 
##        0        0        0      177        0        0        0        0 
## Embarked 
##        0

Se puede observar que la columna Age contiene 177 valores nulos. Existen diferentes políticas para el tratamiento de los valores nulos:

  • Eliminarlos: En Ocasiones, compensa eliminar estas filas, ya que pueden generar distorsiones a la hora de hacer cálculos con las columnas que contienen los valores nulos.

  • Reemplazo: Se podrían reemplazar los valores por la media, la mediana o la moda. Estas medidas se pueden intentar particular en función de otras columnas para que no siempre sean los mismos para todas las entradas nulas.

  • Asignación de una categoría: Si se discretizan los datos en, por ejemplo, rangos de edad, se puede particularizar todos los valores nulos en una categoría especial llamada “edad desconocida”.

  • Predicción de los valores nulos: Por último, se pueden inferir los valores mediante predicciones.

En este caso, se van a inferir los valores en función de otros parámentros. Para hacer esto, partimos de que es muy probable que la edad media de las personas que viajan en Pclass3 es diferente a la edad media de las personas que viajan en Pclass1. Además, esa edad será diferente en función de si estamos ante un hombre o una mujer. Por tanto, para inferir los valores de edad perdidos, se agrupa por Sex y Pclass, para posteriormente calcular las medianas de cada serie agrupada.

Primero, se calcula la media y la mediana de edad en función de la clase y el género del pasajero.

by_sex_class <- titanicData %>% group_by(Sex, Pclass)  %>% summarise(mean = mean(Age, na.rm = TRUE), median = median(Age, na.rm = TRUE))
                      
by_sex_class
## # A tibble: 6 x 4
## # Groups:   Sex [2]
##   Sex    Pclass  mean median
##   <fct>  <fct>  <dbl>  <dbl>
## 1 female 1       34.6   35  
## 2 female 2       28.7   28  
## 3 female 3       21.8   21.5
## 4 male   1       41.3   40  
## 5 male   2       30.7   30  
## 6 male   3       26.5   25

Se observa que la media y la mediana de edad varía en función del género y la clase en la que viajaban. Se procede a rellenar los valores nulos en la columna de edad por los valores de mediana en función de Sex y Pclass.

titanicData$Age[titanicData$Sex == "female" & titanicData$Pclass == "1" & is.na(titanicData$Age)] <- by_sex_class$median[by_sex_class$Sex == "female" & by_sex_class$Pclass == "1"]

titanicData$Age[titanicData$Sex == "female" & titanicData$Pclass == "2" & is.na(titanicData$Age)] <- by_sex_class$median[by_sex_class$Sex == "female" & by_sex_class$Pclass == "2"]

titanicData$Age[titanicData$Sex == "female" & titanicData$Pclass == "3" & is.na(titanicData$Age)] <- by_sex_class$median[by_sex_class$Sex == "female" & by_sex_class$Pclass == "3"]

titanicData$Age[titanicData$Sex == "male" & titanicData$Pclass == "1" & is.na(titanicData$Age)] <- by_sex_class$median[by_sex_class$Sex == "male" & by_sex_class$Pclass == "1"]

titanicData$Age[titanicData$Sex == "male" & titanicData$Pclass == "2" & is.na(titanicData$Age)] <- by_sex_class$median[by_sex_class$Sex == "male" & by_sex_class$Pclass == "2"]

titanicData$Age[titanicData$Sex == "male" & titanicData$Pclass == "3" & is.na(titanicData$Age)] <- by_sex_class$median[by_sex_class$Sex == "male" & by_sex_class$Pclass == "3"]

Se vuelve a comprobar si existen valores nulos.

colSums(is.na(titanicData))
## Survived   Pclass      Sex      Age    SibSp    Parch     Fare    Cabin 
##        0        0        0        0        0        0        0        0 
## Embarked 
##        0

Se han eliminado los valores nulos. Ahora, se comprueba que el summary no difiere mucho del original.

summary(titanicData)
##  Survived Pclass      Sex           Age        SibSp   Parch        Fare       
##  0:549    1:216   female:314   Min.   : 0.42   0:608   0:678   Min.   :  0.00  
##  1:342    2:184   male  :577   1st Qu.:21.50   1:209   1:118   1st Qu.:  7.91  
##           3:491                Median :26.00   2: 28   2: 80   Median : 14.45  
##                                Mean   :29.11   3: 16   3:  5   Mean   : 32.20  
##                                3rd Qu.:36.00   4: 18   4:  4   3rd Qu.: 31.00  
##                                Max.   :80.00   5:  5   5:  5   Max.   :512.33  
##                                                8:  7   6:  1                   
##          Cabin     Embarked
##             :687    :  2   
##  B96 B98    :  4   C:168   
##  C23 C25 C27:  4   Q: 77   
##  G6         :  4   S:644   
##  C22 C26    :  3           
##  D          :  3           
##  (Other)    :186

La media de edad ha bajado ligeramente, pero en general los datos se mantienen estables aún habiendo inferido 177 entradas.

En el summary se pueden observar diferentes columnas con datos anómalos o vacíos. Se procede a analizar uno a uno cada caso.

La columna SibSp contiene muchos valores a 0. En esta columna este valor es perfectamente normal, ya que indica el número de hermanos o esposas a bordo del barco para cada persona.

La columna Parch también contienen valores a 0, pero también cuadra, ya que este campo indica el núero de padres o hijos a bordo.

La columna Fare indica el precio que el pasajero pagó para estar en el barco. El mínimno de esta columna es 0, lo cual no tendría demasiado sentido. Se procede a mirar cuántas entradas tienen 0 en el precio.

filter(titanicData, Fare == 0)
##    Survived Pclass  Sex Age SibSp Parch Fare Cabin Embarked
## 1         0      3 male  36     0     0    0              S
## 2         0      1 male  40     0     0    0   B94        S
## 3         1      3 male  25     0     0    0              S
## 4         0      2 male  30     0     0    0              S
## 5         0      3 male  19     0     0    0              S
## 6         0      2 male  30     0     0    0              S
## 7         0      2 male  30     0     0    0              S
## 8         0      2 male  30     0     0    0              S
## 9         0      3 male  49     0     0    0              S
## 10        0      1 male  40     0     0    0              S
## 11        0      2 male  30     0     0    0              S
## 12        0      2 male  30     0     0    0              S
## 13        0      1 male  39     0     0    0   A36        S
## 14        0      1 male  40     0     0    0  B102        S
## 15        0      1 male  38     0     0    0              S

Es destacable que todas aquellas entradas que tienen Fare = 0 son de hombres. Dado que no han pagado nada, estas personas podrían ser tripulación del barco, por lo que se mantienen estas entradas.

La columna Cabin contiene muchísimos valores nulos, y no se puede inferir de ninguna manera. Tampoco parece que aporte demasiada información útil, por lo que se desecha.

titanicData <- subset(titanicData, select = -c(Cabin))

Por último, la columna Embarked contiene dos valores nulos que sí serían interesantes de completar. En este caso, para evitar tener 4 categorías y que se distorsionen un poco esos datos, se imputan estos dos valores con la moda, es decir, con el valor “S”.

titanicData$Embarked[titanicData$Embarked == ""] <- "S"

# Se elimina la clase sobrante.

titanicData$Embarked <- as.factor(as.character(titanicData$Embarked))

Se hace un último summary para comprobar que todo ha quedado correctamente.

summary(titanicData)
##  Survived Pclass      Sex           Age        SibSp   Parch        Fare       
##  0:549    1:216   female:314   Min.   : 0.42   0:608   0:678   Min.   :  0.00  
##  1:342    2:184   male  :577   1st Qu.:21.50   1:209   1:118   1st Qu.:  7.91  
##           3:491                Median :26.00   2: 28   2: 80   Median : 14.45  
##                                Mean   :29.11   3: 16   3:  5   Mean   : 32.20  
##                                3rd Qu.:36.00   4: 18   4:  4   3rd Qu.: 31.00  
##                                Max.   :80.00   5:  5   5:  5   Max.   :512.33  
##                                                8:  7   6:  1                   
##  Embarked
##  C:168   
##  Q: 77   
##  S:646   
##          
##          
##          
## 

3.2 Identificación y tratamiento de valores extremos.

La mejor manera de tratar los valores extremos es ir mostrando los diferentes valores de columnas numéricas en un diagrama de cajas y bigotes o boxplot. Para la realización de estos diagramas se ha utilizado la librería Plotly.

fig <- plot_ly(y = titanicData$Age, type = "box", name = "Edad")

fig

Aunque vemos unos cuantos outliers en el campo Edad, son perfectamente normales.

Veamos la columna Fare.

fig <- plot_ly(y = titanicData$Fare, type = "box", name = "Precio del Ticket")

fig

Se observan bastantes outliers, pero hay un precio que destaca más que los demás. Vamos a analizar esas filas.

filter(titanicData, Fare > 500)
##   Survived Pclass    Sex Age SibSp Parch     Fare Embarked
## 1        1      1 female  35     0     0 512.3292        C
## 2        1      1   male  36     0     1 512.3292        C
## 3        1      1   male  35     0     0 512.3292        C

Las tres filas pertenecen a personas del mismo rango de edad que embarcaron desde el mismo puerto. Por la exactitud de los datos y su homogeneidad, parecen datos correctos, por lo que se mantienen en el dataset.

Adicionalmente Crearemos un dato en base a la edad, diviendola en segmentos para así poder realizar un análisis por categorías:

titanicData$AgeGroup <- cut(titanicData$Age, breaks = c(0,10,20,30,40,50,60,70,100), labels = c("0-9", "10-19", "20-29", "30-39","40-49","50-59","60-69","70-79"))
plot(titanicData$AgeGroup)

4 Análisis de los datos

En esta sección dividiremos el conjunto de datos en subconjuntos para analizar y compararlos entre ellos.

4.1 Selección de grupos de datos a analizar/comparar

El conjunto de datos lo analizaremos en fución a los siguientes grupos para determinar si guardan relación con la supervivencia:

  • Rango de edad Columna AgeGroup
  • Poder adquisitivo Columna Pclass nos dice en que clase embarcaron, lo que nos ayuda a inferir su nivel adquisitivo.
  • Sexo Columna Sex.

Vamos a mostrar con gráficas cada uno de los grupos nombrados junto con la supervivencia.

4.1.1 Rango de edad

ggplot(data=titanicData[1:nrow(titanicData),],aes(x=AgeGroup,fill=Survived))+geom_bar()

4.1.2 Nivel adquisitivo

ggplot(data=titanicData[1:nrow(titanicData),],aes(x=Pclass,fill=Survived))+geom_bar()

4.1.3 Sexo

ggplot(data=titanicData[1:nrow(titanicData),],aes(x=Sex,fill=Survived))+geom_bar()

4.2 Comprobación de normalidad y homogeneidad de la varianza.

Para comprobar la normalidad se va a utilizar la prueba de la normalidad de Anderson Darling, se comprueba que el p-valor sea superior a 0.05.

alpha = 0.05
col.names = colnames(titanicData)
  for (i in 1:ncol(titanicData)) {
    if (i == 1) cat("Variables que no siguen una distribución normal:\n")
    if (is.integer(titanicData[,i]) | is.numeric(titanicData[,i])) {
    p_val = ad.test(titanicData[,i])$p.value
    if (p_val < alpha) {
      cat(col.names[i])
      # Format output
      if (i < ncol(titanicData) - 1) cat(", ")
      if (i %% 3 == 0) cat("\n")
    }
  }
}
## Variables que no siguen una distribución normal:
## Age, Fare,

4.3 Aplicación de pruebas estadísticas

4.3.1 Prueba 1:

4.3.2 Prueba 2:

4.3.3 Prueba 3:

5 Representación de los resultados

6 Resolución del problema

6.1 Conclusiones